package com.bestcem.xm.common.core.sql;

import com.alibaba.fastjson.JSONObject;
import com.bestcem.xm.common.core.uitls.DateUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.bestcem.xm.common.core.sql.ConditionConstant.DETAIL_LEFT_OPEN;
import static com.bestcem.xm.common.core.sql.ConditionConstant.DETAIL_RIGHT_OPEN;


/**
 * @author ming.xue <ming.xue@idiaoyan.com>
 * @desc 简单条件代理
 * @date 2021-05-25
 */
@Slf4j
@Getter
@Setter
public class ConditionProxy {
    /**
     * 被代理的原始简单条件
     */
    private final JSONObject condition;

    private boolean isOr;

    private List<ConditionProxy> conditionProxies;

    //private List<ConditionUnitProxy> conditionUnitProxies;

    /**
     * 字段映射
     */
    private Map<String, ColumnDesc> columnMapping = new HashMap<>();

    private String tableAlias;

    public ConditionProxy(JSONObject condition) {
        if (condition == null) {
            throw new ConditionGrammarException("condition missing");
        }
        this.condition = condition;
    }

    public ConditionProxy(JSONObject condition, Map<String, ColumnDesc> columnMapping) {
        this(condition);
        this.columnMapping = columnMapping;
    }

    /*public ConditionProxy(JSONObject condition, boolean isOr, List<ConditionProxy> conditionProxies, List<ConditionUnitProxy> conditionUnitProxies) {
        this(condition);
        this.isOr = isOr;
        this.conditionProxies = conditionProxies;
        this.conditionUnitProxies = conditionUnitProxies;
    }*/

    public Map<String, ColumnDesc> getColumnMapping() {
        if (this.columnMapping == null) {
            this.columnMapping = new HashMap<>();
        }
        return this.columnMapping;
    }

    public void setTableAlias(String tableAlias) {
        this.tableAlias = tableAlias;
        String tableName = condition.getString("table");
        if(StringUtils.isNotBlank(tableName)){
            this.tableAlias = tableName;
        }
    }

    /**
     * 获取数据类型
     *
     * @return
     */
    public String getDataType() {
        String dataType = condition.getString(ConditionConstant.TYPE);
        if (StringUtils.isBlank(dataType)) {
            throw new ConditionGrammarException("condition.type missing");
        }
        return dataType;
    }

    /**
     * 获取操作符
     *
     * @return
     */
    public String getOperator() {
        String operator = condition.getString(ConditionConstant.OPERATOR);
        if (StringUtils.isBlank(operator)) {
            throw new ConditionGrammarException("condition.operator missing");
        }
        return operator;
    }

    /**
     * 获取字段名称
     *
     * @return
     */
    public String getName() {
        String name = condition.getString(ConditionConstant.NAME);
        if (StringUtils.isBlank(name)) {
            throw new ConditionGrammarException("condition.name missing");
        }

        ColumnDesc columnDesc = this.getColumnMapping().get(name);
        if (columnDesc != null && StringUtils.isNotBlank(columnDesc.getName())) {
            name = columnDesc.getName();
        }
        if (StringUtils.isNotBlank(tableAlias)) {
            name = String.format("%s.%s", tableAlias, name);
        }

        return name;
    }

    /**
     * 获取地址字段名称
     *
     * @return
     */
    public String getAddressName() {
        String name = this.getName();
        int lastSeparatorIndex = name.lastIndexOf(".");
        if (lastSeparatorIndex <= 0 || name.endsWith(".")) {
            throw new ConditionGrammarException(String.format("condition.name malformed, %s", name));
        }

        String addressFieldName = name.substring(0, lastSeparatorIndex);
        String addressFieldIndex = name.substring(lastSeparatorIndex + 1);
        return String.format("get_address_part(%s, %s)", addressFieldName, addressFieldIndex);
    }

    /**
     * 获取值
     *
     * @return
     */
    public String getStringValue() {
        return this.getStringValue(false);
    }

    /**
     * 获取值
     *
     * @param notBlank
     * @return
     */
    public String getStringValue(boolean notBlank) {
        String value = condition.getString(ConditionConstant.VALUE);
        if (notBlank && StringUtils.isBlank(value)) {
            throw new ConditionGrammarException("condition.value missing");
        }
        return value;
    }

    /**
     * 获取详情
     *
     * @return
     */
    public JSONObject getDetail() {
        return condition.getJSONObject(ConditionConstant.DETAIL);
    }

    /**
     * 是否包含左边界
     *
     * @return
     */
    public boolean isLeftOpen() {
        JSONObject detail = this.getDetail();
        if (CollectionUtils.isEmpty(detail)) {
            return false;
        }

        return detail.getBooleanValue(DETAIL_LEFT_OPEN);
    }

    /**
     * 是否包含右边界
     *
     * @return
     */
    public boolean isRightOpen() {
        JSONObject detail = this.getDetail();
        if (CollectionUtils.isEmpty(detail)) {
            return false;
        }

        return detail.getBooleanValue(DETAIL_RIGHT_OPEN);
    }

    /**
     * 获取原始值
     *
     * @param key
     * @return
     */
    public Object getRawValue(String key) {
        return condition.get(key);
    }

    /**
     * 获取原始值
     *
     * @param key
     * @return
     */
    public Object getRawDetailValue(String key) {
        JSONObject detail = condition.getJSONObject(ConditionConstant.DETAIL);
        if (detail == null) {
            return null;
        };
        return detail.get(key);
    }

    /**
     * 获取值
     *
     * @return
     */
    public Object getValue() {
        String conditionValueStr = this.getStringValue();
        if (StringUtils.isBlank(conditionValueStr)) {
            throw new ConditionGrammarException("condition.value missing");
        }

        Object result = null;
        String dataType = StringUtils.trimToEmpty(this.getDataType());
        switch (dataType) {
            case ConditionConstant.FieldTypes.STRING:
            case ConditionConstant.FieldTypes.SELECT:
            case ConditionConstant.FieldTypes.LEVEL:
            case ConditionConstant.FieldTypes.LABEL:
            case ConditionConstant.FieldTypes.ADDRESS:
            case ConditionConstant.FieldTypes.GROUP:
                result = this.getMappedValue(conditionValueStr);
                break;
            case ConditionConstant.FieldTypes.NUMBER:
                result = ConditionDataConverter.convertToDouble(conditionValueStr);
                break;
            case ConditionConstant.FieldTypes.DATETIME:
                result = ConditionDataConverter.convertToDate(conditionValueStr, DateUtil.UTC_TIME_ZONE);
                break;
        }

        if (result == null) {
            throw new ConditionGrammarException("condition.value missing");
        }
        return result;
    }

    /**
     * 获取值列表
     *
     * @return
     */
    public List<Object> listValues() {
        List<Object> rawConditionValues = (List<Object>) this.getRawValue(ConditionConstant.VALUE);
        if (CollectionUtils.isEmpty(rawConditionValues)) {
            throw new ConditionGrammarException("condition.value array missing");
        }

        List<Object> result = null;
        String dataType = StringUtils.trimToEmpty(this.getDataType());
        switch (dataType) {
            case ConditionConstant.FieldTypes.STRING:
            case ConditionConstant.FieldTypes.SELECT:
            case ConditionConstant.FieldTypes.LEVEL:
            case ConditionConstant.FieldTypes.LABEL:
            case ConditionConstant.FieldTypes.ADDRESS:
                result = this.getMappedValues(rawConditionValues);
                break;
            case ConditionConstant.FieldTypes.NUMBER:
                result = rawConditionValues.stream().map(item -> item == null ? null : item.toString())
                        .map(ConditionDataConverter::convertToDouble).collect(Collectors.toList());
                break;
            case ConditionConstant.FieldTypes.DATETIME:
                result = rawConditionValues.stream().map(item -> item == null ? null : item.toString())
                        .map(ConditionDataConverter::convertToDate).collect(Collectors.toList());
                break;
        }

        if (CollectionUtils.isEmpty(result)) {
            throw new ConditionGrammarException("condition.value array missing");
        }
        return result;
    }

    public List<Object> getMappedValues(List<Object> rawConditionValues) {
        String name = condition.getString(ConditionConstant.NAME);
        if (StringUtils.isBlank(name)) {
            throw new ConditionGrammarException("condition.name missing");
        }

        ColumnDesc columnDesc = this.columnMapping.get(name);
        if (columnDesc != null && columnDesc.getJdbcType() != null) {
            Integer jdbcType = columnDesc.getJdbcType();
            if (Objects.equals(jdbcType, ColumnDesc.JDBC_TYPE_BINARY)) {
                return rawConditionValues.stream().map(item -> item == null ? null : item.toString())
                        .map(this::parseObjectId).collect(Collectors.toList());
            } else if (Objects.equals(jdbcType, ColumnDesc.JDBC_TYPE_INTEGER)) {
                return rawConditionValues.stream().map(item -> item == null ? null : item.toString())
                        .map(ConditionDataConverter::convertToInteger).collect(Collectors.toList());
            }
        }
        return rawConditionValues;
    }

    public Object getMappedValue(String valueStr) {
        if (valueStr == null) {
            return null;
        }

        String name = condition.getString(ConditionConstant.NAME);
        if (StringUtils.isBlank(name)) {
            throw new ConditionGrammarException("condition.name missing");
        }

        ColumnDesc columnDesc = this.columnMapping.get(name);
        if (columnDesc != null && columnDesc.getJdbcType() != null) {
            Integer jdbcType = columnDesc.getJdbcType();
            if (Objects.equals(jdbcType, ColumnDesc.JDBC_TYPE_BINARY)) {
                return this.parseObjectId(valueStr);
            } else if (Objects.equals(jdbcType, ColumnDesc.JDBC_TYPE_INTEGER)) {
                return ConditionDataConverter.convertToInteger(valueStr);
            }
        }
        return valueStr;
    }

    private byte[] parseObjectId(String hexStr) {
        if (!ObjectId.isValid(hexStr)) {
            throw new IllegalArgumentException(String.format("invalid hexadecimal representation of an ObjectId: %s",
                    hexStr));
        }

        try {
            byte[] binary = Hex.decodeHex(hexStr);
            if (log.isDebugEnabled()) {
                log.debug("parse ObjectId, hexStr: {}, binary: {}", hexStr, binary);
            }
            return binary;
        } catch (DecoderException e) {
            String msg = String.format("decode ObjectId failed: %s", hexStr);
            log.error(msg, e);
            throw new IllegalArgumentException(msg, e);
        }
    }
}
